﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Drawing;
using System.IO;
using System.Text;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.StsAdmin;
using Microsoft.Win32;

namespace SPNotesFor.Administration
{
	/// <summary>
	/// Implements the Register iFilter in Search Server command.
	/// </summary>
	public sealed class FilterCommand : ISPStsadmCommand, IComparer<SPServer>
	{
		private StringBuilder result;
		private SearchExtender extender;
		private SortedList<SPServer, RegistryKey> indexingServers;


		public string GetHelpMessage(string command)
		{
			return "\r\n    -ext <extension>\r\n    [-filter <CLASSID> (default from HKEY_CLASSES_ROOT\\.ext\\PersistentHandler]\r\n    [-mime <mime-types> (default from HKEY_CLASSES_ROOT\\.ext\\ContentType)]\r\n    [-iconfile <path to filename>]\r\n\r\n    -ext pdf\r\n      -filter <Adobe|Foxit|PDFlib|(CLASSID)>\r\n      [-icon (use embedded icpdf.gif)]\r\n      [-iconfile <path to filename>]\r\n\r\nExamples:\r\n    stsadm -o searchfilter -ext pdf -filter Foxit –icon\r\n    stsadm -o searchfilter -ext pdf -filter PDFlib –iconfile \"C:\\Temp\\icpdf.gif\"\r\n    stsadm -o searchfilter -ext dwf -mime application/dwf,application/x-dwf -filter {D4AA3B39-7393-4A19-A4B5-2749DB0F4ECB}\r\n    stsadm -o searchfilter -ext msg -iconfile \"C:\\icmsg.gif\"";
		}

		public int Run(string command, StringDictionary keyValues, out string output)
		{
			result = new StringBuilder();
			extender = new SearchExtender(result);
			try
			{
				return this.RegisterFilter(keyValues, out output);
			}
			finally
			{
				// Cleanup remote registry key
				if (indexingServers != null)
				{
					for (int i = 0; i < indexingServers.Values.Count; i++)
						indexingServers.Values[i].Close();
				}
			}
		}

		//____________________________________________________________________
		//
		// Command Implementation

		#region RegisterFilter

		private int RegisterFilter(StringDictionary keyValues, out string output)
		{
			List<String> requiredParams = new List<String>();
			requiredParams.Add("ext");
			if (keyValues["ext"] == "pdf") requiredParams.Add("filter");
			if (!ValidateRequiredParameters(keyValues, requiredParams))
			{
				if (keyValues["ext"] == "pdf")
				{
					result.Append("You can download and intall either:\r\n"
						+ "Foxit Filter     » http://www.foxitsoftware.com/pdf/ifilter/\r\n"
						+ "Adobe Filter x32 » http://www.adobe.com/support/downloads/detail.jsp?ftpID=2611\r\n"
						+ "Adobe Filter x64 » http://www.adobe.com/support/downloads/detail.jsp?ftpID=4025\r\n"
						+ "PDFlib TET Filter» http://www.pdflib.com/products/tet/");
				}

				// Returns -2 to tell stsadm to show the help usage.
				output = result.ToString();
				return -2;
			}


			try
			{
				indexingServers = FindIndexingServers();

				if (!ValidateParameters(keyValues))
				{
					output = result.ToString();
					return -1;
				}

				// Update all the Web Front-End server
				foreach (SPServer server in SPFarm.Local.Servers)
				{
					if (server.Role == SPServerRole.WebFrontEnd || server.Role == SPServerRole.SingleServer)
					{
						extender.ApplyOnWebFront(server);
					}
				}

				// Update all the Indexing Search server
				var en = indexingServers.GetEnumerator();
				while (en.MoveNext())
				{
					try
					{
						extender.ApplyOnIndexingServer(en.Current.Key, en.Current.Value);
					}
					catch (Exception exc)
					{
						if (exc is OutOfMemoryException || exc is StackOverflowException || exc is System.Threading.ThreadAbortException)
							throw;

						result.AppendFormat("Error: {0}\r\n", exc.ToString());
					}
				}
			}
			finally
			{
				if (extender.HasUninstalledFilter)
					result.Append("Completed but some server has been skipped.");

				output = result.ToString();
			}

			return -1;
		}

		#endregion

		#region ValidateRequiredParameters

		private bool ValidateRequiredParameters(StringDictionary parameters, IList<String> names)
		{
			bool isValid = true;
			for (int i = 0; i < names.Count; i++)
			{
				if (String.IsNullOrEmpty(parameters[names[i]]))
				{
					isValid = false;
					result.AppendFormat("Missing required argument: {0}.\r\n", names[i]);
				}
			}

			return isValid;
		}

		#endregion

		#region ValidateParameters

		private bool ValidateParameters(StringDictionary keyValues)
		{
			bool isValid = true;

			extender.MimeTypes = keyValues["mime"];
			extender.Extension = keyValues["ext"];
			string filter = keyValues["filter"];
			string iconfile = keyValues["iconfile"];



			if (filter == null)
			{
				String filterName;
				filter = DiscoverFilter(out filterName);
				if (String.IsNullOrEmpty(filter))
				{
					result.AppendLine("Invalid parameter: filter could not be resolved.\r\nRetry and specify the filter argument.");
					return false;
				}

				extender.Filter = new ProductFilter { ClassID = filter, Name = filterName };
			}


			if (extender.Extension == "pdf")
			{
				extender.MimeTypes = "application/pdf";
				extender.Icon = keyValues["icon"];
				if (extender.Icon != null)
				{
					extender.Icon = "ICPDF.GIF";
					extender.IconData = (byte[]) new ImageConverter().ConvertTo(Properties.Resources.ICPDF, typeof(byte[]));
				}

				switch (filter.ToLowerInvariant())
				{
					case "adobe":
						// Search first for the 64-bit version than fallback on the 32-bit.
						extender.Filter = new ProductFilter { ClassID = KnownPdfFilter.Adobe64FilterID, Name = "Adobe Filter 64-bit" };
						extender.FallbackFilter = new ProductFilter { ClassID = KnownPdfFilter.Adobe32FilterID, Name = "Adobe Filter 32-bit" };
						break;
					case "foxit":
						extender.Filter = new ProductFilter { ClassID = KnownPdfFilter.FoxitFilterID, Name = "Foxit PDF Filter" };
						break;
					case "pdflib":
						extender.Filter = new ProductFilter { ClassID = KnownPdfFilter.PDFlibFilterID, Name = "PDFlib TET Filter" };
						break;
					default:
						Guid guid;
						if (!TryParseGuid(filter, out guid))
						{
							result.AppendLine("Expected 'Adobe', 'Foxit', 'PDFlib' or the CLASSID of another product.");
							return false;
						}

						filter = guid.ToString("B");
						extender.Filter = new ProductFilter { ClassID = filter, Name = FindFilterName(filter) };
						break;
				}
			}
			else
			{
				// Ensure filter is a guid
				Guid guid;
				if (!TryParseGuid(filter, out guid))
				{
					isValid = false;
					result.AppendLine("Invalid parameter: filter is not a valid GUID.");
				}
				else
				{
					filter = guid.ToString("B");
					extender.Filter = new ProductFilter { ClassID = filter, Name = FindFilterName(filter) };
				}


				if (extender.MimeTypes == null)
				{
					extender.MimeTypes = DiscoverContentType();
					if (String.IsNullOrEmpty(extender.MimeTypes))
					{
						isValid = false;
						result.AppendLine("Invalid parameter: mime-type could not be resolved.\r\nRetry and specify the mime argument.");
					}
				}
			}

			if (iconfile != null)
				isValid = ReadIconFile(iconfile);

			return isValid;
		}

		#endregion

		#region FindIndexingServers

		/// <summary>
		/// Cache the list of indexing servers.
		/// </summary>
		private SortedList<SPServer, RegistryKey> FindIndexingServers()
		{
			var indexingServers = new SortedList<SPServer, RegistryKey>(this);

			if (SPServer.LocalServerRole == SPServerRole.SingleServer)
			{
				indexingServers.Add(SPServer.Local, Registry.ClassesRoot);
			}
			else
			{
				foreach (SPServer server in SPFarm.Local.Servers)
				{
					foreach (SPServiceInstance inst in server.ServiceInstances)
					{
						if (inst.Status != SPObjectStatus.Online || inst.Hidden) continue;

						// Keep servers where MOSS / WSS Search service is running
						if (inst.TypeName == "Office SharePoint Server Search" || inst.TypeName == "Windows SharePoint Services Search")
						{
							try
							{
								indexingServers.Add(server, RegistryKey.OpenRemoteBaseKey(RegistryHive.ClassesRoot, server.Address));
							}
							catch (IOException)
							{
								result.AppendFormat("Skipping this machine \"{0}\" because it does not respond.\r\n", server.Address);
							}
							break;
						}
					}
				}
			}

			return indexingServers;
		}

		#endregion

		#region TryParseGuid

		private static bool TryParseGuid(string value, out Guid guid)
		{
			try
			{
				guid = new Guid(value);
				return true;
			}
			catch (FormatException) { }
			catch (OverflowException) { }

			guid = Guid.Empty;
			return false;
		}

		#endregion

		#region ReadIconFile

		private bool ReadIconFile(string iconfile)
		{
			string filename;
			try
			{
				filename = Path.GetFullPath(iconfile);
			}
			catch (Exception exc)
			{
				if (!(exc is PathTooLongException || exc is NotSupportedException || exc is ArgumentException))
					throw;

				result.Append("Invalid parameter: iconfile. \n{0}").AppendLine(exc.Message);
				return false;
			}

			if (!File.Exists(filename))
			{
				result.AppendFormat("Invalid parameter: iconfile. No file at \"{0}\".", filename).AppendLine();
				return false;
			}
			else
			{
				extender.Icon = Path.GetFileName(filename).ToUpper();
				extender.IconData = File.ReadAllBytes(filename);
			}

			return true;
		}

		#endregion

		#region DiscoverContentType

		/// <summary>
		/// Search into the indexing servers if a Content Type can be associated to an extension.
		/// </summary>
		/// <returns>Returns null if not resolved.</returns>
		private string DiscoverContentType()
		{
			string ct;
			foreach (var classesRoot in indexingServers.Values)
			{
				using (RegistryKey key = classesRoot.OpenSubKey("." + extender.Extension))
				{
					if (key != null)
					{
						ct = key.GetValue("Content Type") as String;
						if (!String.IsNullOrEmpty(ct))
							return ct;
					}
				}
			}

			return null;
		}

		#endregion

		#region DiscoverFilter

		/// <summary>
		/// Search into the indexing servers if a filter product can be associated to an extension.
		/// </summary>
		/// <param name="name">The name of the product.</param>
		/// <returns>Returns null if not resolved.</returns>
		private string DiscoverFilter(out String name)
		{
			string classID;
			name = null;
			string keyName = String.Format(@".{0}\PersistentHandler", extender.Extension);

			foreach (var regKeyClassesRoot in indexingServers.Values)
			{
				using (RegistryKey key = regKeyClassesRoot.OpenSubKey(keyName))
				{
					if (key != null)
					{
						classID = key.GetValue(String.Empty) as String;
						if (!String.IsNullOrEmpty(classID))
						{
							using (RegistryKey clsKey = regKeyClassesRoot.OpenSubKey(@"CLSID\" + classID))
							{
								if (clsKey != null)
									name = key.GetValue(String.Empty) as String;
							}

							return classID;
						}
					}
				}
			}

			return null;
		}

		#endregion

		#region FindFilterName

		/// <summary>
		/// Search into the indexing servers after the display name of a filter.
		/// </summary>
		/// <returns>Returns null if not resolved.</returns>
		private string FindFilterName(String classID)
		{
			string keyName = String.Format(@".{0}\PersistentHandler", extender.Extension);

			foreach (var regKeyClassesRoot in indexingServers.Values)
			{
				using (RegistryKey clsKey = regKeyClassesRoot.OpenSubKey(@"CLSID\" + classID))
				{
					if (clsKey != null)
						return clsKey.GetValue(String.Empty) as String;
				}
			}

			return "<Unknow>";
		}

		#endregion

		#region IComparer<SPServer>

		int IComparer<SPServer>.Compare(SPServer x, SPServer y)
		{
			return x.Id.CompareTo(y.Id);
		}

		#endregion
	}
}